package com.googlecode.hypersphere.xsd;

import java.io.File;
import java.util.Iterator;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.DefaultAttribute;
import org.dom4j.tree.DefaultElement;

public class EjbJarXsdGenerator {

	private static final String ATTRIBUTE = "attribute";

	private static final String COMPLEX_TYPE = "complexType";

	private static final String XSD_STRING = "xsd:string";

	private static final String ELEMENT = "element";

	private static final String MAX_OCCURS = "maxOccurs";

	private static final String NAME = "name";

	private final static Namespace NAMESPACE = new Namespace("xsd",
			"http://www.w3.org/2001/XMLSchema");

	private static final String REF = "ref";

	private static final String SEQUENCE = "sequence";

	private static final String TYPE = "type";

	private static final String UNBOUNDED = "unbounded";

	private static void addAttributes(EjbJarElement ejbElement,
			Element complexElement) {
		for (Iterator iter = ejbElement.attributeIterator(); iter.hasNext();) {
			Attribute attribute = (Attribute) iter.next();
			Element attributeElement = createXsdElement(ATTRIBUTE);
			attributeElement
					.add(new DefaultAttribute(NAME, attribute.getName()));
			complexElement.add(attributeElement);
		}
	}

	private static void addReferenceAttribute(Element xsdRefElement, EjbJarElement ejbNextElement) {
		xsdRefElement.add(new DefaultAttribute(REF, ejbNextElement.getName()));
	}

	private static void addReferences(EjbJarElement ejbElement,
			Element xsdComplexElement) {
		if (!ejbElement.elementIterator().hasNext()) {
			return;
		}
		Element xsdSequenceElement = createXsdElement(SEQUENCE);
		for (Iterator<EjbJarElement> iter = ejbElement.elementIterator(); iter
				.hasNext();) {
			EjbJarElement ejbNextElement = iter.next();
			Element xsdRefElement = createXsdElement(ELEMENT);
			addReferenceAttribute(xsdRefElement, ejbNextElement);
			if (ejbNextElement.isUnbound()) {
				addUnboundedAttribute(xsdRefElement);
			}
			xsdSequenceElement.add(xsdRefElement);
		}
		xsdComplexElement.add(xsdSequenceElement);
	}

	private static void addUnboundedAttribute(Element xsdRefElement) {
		xsdRefElement.add(new DefaultAttribute(MAX_OCCURS, UNBOUNDED));
	}

	private static Element createRootElement() {
		Element root = new DefaultElement("schema", EjbJarXsdGenerator.NAMESPACE);
		root.add(new Namespace("", "http://java.sun.com/xml/ns/j2ee"));
		root.add(new DefaultAttribute("targetNamespace",
				"http://java.sun.com/xml/ns/j2ee"));
		return root;
	}

	private static DefaultElement createXsdElement(String elementName) {
		return new DefaultElement(elementName, EjbJarXsdGenerator.NAMESPACE);
	}

	public static Element generate(File xmlFile) {
		SAXReader xmlReader = new SAXReader();
		Document xmlDoc;
		try {
			xmlDoc = xmlReader.read(xmlFile);
		} catch (DocumentException e) {
			throw new IllegalArgumentException(e);
		}
		EjbJar ejbJar = new EjbJar(xmlDoc.getRootElement());
		Element xsdRoot = EjbJarXsdGenerator.createRootElement();
		EjbJarXsdGenerator.generateForAllElements(ejbJar, xsdRoot);
		return xsdRoot;
	}

	private static void generateForAllElements(EjbJar ejbJar, Element xsdRoot) {
		for (Iterator<EjbJarElement> iter = ejbJar.childIterator(); iter.hasNext();) {
			EjbJarElement ejbElement = iter.next();
			Element xsdElement = createXsdElement(ELEMENT);
			xsdElement.addAttribute(NAME, ejbElement.getName());
			if (hasNeitherChildrenNorAttributes(ejbElement)) {
				xsdElement.addAttribute(TYPE, XSD_STRING);
			} else {
				Element complexElement = createXsdElement(COMPLEX_TYPE);
				addReferences(ejbElement, complexElement);
				addAttributes(ejbElement, complexElement);
				xsdElement.add(complexElement);
			}
			xsdRoot.add(xsdElement);
		}
	}

	private static boolean hasNeitherChildrenNorAttributes(EjbJarElement ejbElement) {
		return !ejbElement.elementIterator().hasNext()
				&& !ejbElement.attributeIterator().hasNext();
	}
}
